/*
frigOS
Licensed under GPLv3
*/
#ifndef __KERNEL_H__
#define __KERNEL_H__

#include <stdint.h>
#include <setjmp.h>
#include "defs.h"

// the maxumum number of threads that can be running concurrently
// this includes the idle thread
#ifndef MAXIMUM_TASKS
#   define MAXIMUM_TASKS 8
#endif

// the maximum number of monitor threads that can be running concurrently
#ifndef MAXIMUM_MONITOR_THREADS
#   define MAXIMUM_MONITOR_THREADS    32
#endif
#define NO_MONITOR_THREAD_SCHEDULED   0xff

// the stack is partitioned into MAXUMUM_TASKS blocks of varying size
// the sized are measured in kB
#ifndef MINIMUM_STACK_SIZE
#   define MINIMUM_STACK_SIZE 256
#endif
#ifndef MEDIUM_STACK_SIZE
#    define MEDIUM_STACK_SIZE 256
#endif
#ifndef MAXIMUM_STACK_SIZE
#    define MAXIMUM_STACK_SIZE 512
#endif

// we include rails around each stack partition so we can detect stack overflows
#define IDLE_STACK_SIZE 128
#define RAIL_STACK_SIZE 9

// total stack size with the appropriate partition sizes
#ifndef STACK_SIZE
#   define STACK_SIZE (IDLE_STACK_SIZE) + (MINIMUM_STACK_SIZE*4) + (MEDIUM_STACK_SIZE*2) + (MAXIMUM_STACK_SIZE) + (RAIL_STACK_SIZE)
#endif

//2 kb

#define RAIL0 STACK_SIZE - 1
#define RAIL1 STACK_SIZE - 2 - IDLE_STACK_SIZE
#define RAIL2 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE
#define RAIL3 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE*2 - 1
#define RAIL4 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE*3 - 2
#define RAIL5 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE*4 - 3
#define RAIL6 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE*4 - 4 - MEDIUM_STACK_SIZE
#define RAIL7 STACK_SIZE - 2 - IDLE_STACK_SIZE - 1 - MINIMUM_STACK_SIZE*4 - 4 - MEDIUM_STACK_SIZE*2 - 1
#define RAIL8 0
#define SCHECK 0xff


enum task_id {IDLE, TID1, TID2, TID3, TID4, TID5, TID6, TID7};
enum task_state {RUNNING, READY, BLOCKED, STOPPED, SLEEPING, WAITING, WAIT_TIMEOUT};
enum task_prioriy {IDLE_PRIORITY, MINIMUM_PRIORITY, MEDIUM_PRIORITY, MAXIMUM_PRIORITY};
enum partition_status {FREE = 1, USED = 0};

// kernel error bit field
enum kernel_err
{
    ERR_STACK_OVERFLOW = (1<<0),    // a thread's stack overflowed
    ERR_USART0 = (1<<1),            // critical error with USART0
    ERR_USART1 = (1<<2)             // critical error with USART1
    // TODO: other error codes
};

// events that can be fired from the kernel
// we can support up to 16 events since we store events as a bit-field
// not all events are implemented yet
typedef enum event_t
{
    // interrupt events
    USART0_RX_EVENT = (1<<0),        // fires from USART0 Rx ISR
    USART0_TX_EVENT = (1<<1),        // fires from USART0 Tx ISR
    USART1_RX_EVENT = (1<<2),        // fires from USART1 Rx ISR
    USART1_TX_EVENT = (1<<3),        // fires from USART1 Tx ISR
    A2D_COMPLETE_EVENT = (1<<4),     // fires from A2D Complete ISR
    // NOT IMPLEMENTED TIMER1_OVEFLOW_EVENT = (1<<5),   // fires from timer 1 overflow ISR
    TIMER2_OVERFLOW_EVENT = (1<<6),  // fires from timer 2 overflow ISR
    TIMER3_OVERFLOW_EVENT = (1<<7),  // fires from timer 3 overflow ISR

    // thread-controlled events
    COMMAND_PACKET_SENT_EVENT = (1<<8),      // fires when a command packet is sent to the servos
    STATUS_PACKET_RECEIVED_EVENT = (1<<9),   // fires when a complete status packet from the servos is received

    // button events
    UP_BUTTON_PRESSED_EVENT = (1<<10),
    DOWN_BUTTON_PRESSED_EVENT = (1<<11),
    LEFT_BUTTON_PRESSED_EVENT = (1<<12),
    RIGHT_BUTTON_PRESSED_EVENT = (1<<13),
    // NOT IMPLEMENTED START_BUTTON_PRESSED_EVENT = (1<<14),

    // User-defined event; may be used for future expansion
    USER_DEF_EVENT1 = (1<<15)
} Event;


// thread control block
// every thread is assigned one of these
typedef struct tcb
{
    uint8_t tid;            // thread ID; unique to each thread running, but may be recycled if a thread is stopped and a new one started
    uint8_t state;          // the thread's state.  see enum task_state
    uint8_t priority;       // the thread's priority. see enum task_priority
    int8_t niceness;        // the thread's niceness; this is used by the scheduler to prevent high priority tasks from starving-out lower-priority ones
    void *stackPtr;         // pointer to this thread's stack partition
    uint16_t sleep;         // used by the sleep and waitTimeout functions to count how long the thread is sleeping
    uint16_t waitingFor;    // used by wait and fireEvent functions to track what events the thread is waiting for
    uint8_t monitorLevel;   // used by startMonitorThread to allow nested asynchronous loops
} TCB;


// monitor thread control block
// can be used in 2 modes:
//  - continuous monitor; execute at regular intervals, i/o is via globals
//  - asynchronous loop condition; uses setjmp/longjmp to break out of parent thread's loop
typedef struct mcb
{
    uint8_t (*execute)(void);   // function to execute
    uint16_t interval;          // ms to elapse between executions
    uint16_t count;             // number of ms that have elapsed
    uint8_t tid;                // unique monitor thread id
    uint8_t parentTid;          // id of parent thread
    uint8_t level;              // the monitorLevel of the parent thread when this monitor was started
    jmp_buf* jmpBuf;            // long-jump buffer used for asynch while looping
} MCB;

// semaphore/mutex
// used to coordinate synchronization between threads
typedef struct semaphore
{
    int8_t value;                       // negative values indicate the semaphore is unlocked; values >=0 indicate the semaphore is locked
    uint8_t waitList[MAXIMUM_TASKS];    // a queue of threads waiting to lock this semaphore
    uint8_t queueLength;                // the length of the wait queue
    uint8_t lockedBy;                   // the TID of the thread that currently owns the lock; set to IDLE of the semaphore is unlocked
} Semaphore;

// stack partition
// every thread is assigned a distinct stack partition
typedef struct partition
{
    void *stackPtr;         // pointer to the start of the partition's allocated memory
    uint16_t stackSize;     // size of the stack partition
    uint8_t status;         // USED or FREE
} Partition;


// keep arrays of control blocks for threads and monitor threads
extern volatile TCB thread[];
extern volatile MCB monitorThread[];

// the entire partitioned stack space is stored as an array so we can check rails
extern volatile uint8_t stack[];

// the currently executing thread; should be treated as read-only except by the scheduler
extern volatile uint8_t currentThread;

// total number of threads currently running
extern volatile uint8_t totalThreads;

// ID of the next available free TCB slot
extern volatile uint8_t tidFree;

// total number of running monitor threads
extern volatile uint8_t totalMonitorThreads;


/* initialization functions */

// boot the FrigOS kernel; initialized hardware (e.g. USART1, USART0, buttons, adc)
// should be called from main before starting any threads or monitors
void boot(void);

// initialize the kernel and set up the TCBs and stack space
// starts the IDLE thread
void initKernel(void);

// initialize the MCBs so we can run monitor threads
void initMonitorThreads(void);

// send a welcome message over USART1 with OS version info
void printBootBanner(void);


/* standard thread controls */

// start a new thread, using the provided function pointer as the thread's entry point
uint8_t startThread(void (*functionPtr)(void *arg), void *arg, uint8_t priority, uint16_t stackSize);

// stop a thread
// WARNING: this will kill the thread, but does not release any semaphores it may own locks on
// use at your own peril
void stopThread(void);

// put a thread to sleep for a given number of system ticks
// system ticks occur 10ms
void sleep(uint16_t ticks);

// used by the context switcher to change between threads
// should only ever be called from within the timer0 interrupt handler
void schedule(void);


/* event controls */

// stop the thread until an event fires
// multiple events can be listened for by using bitwise OR operators
// e.g. wait(USART0_TX_EVENT | USART1_TX_EVENT);
// the thread's TCB.waitingFor will be set to the bitmask of the fired event when the thread resumes
void wait(Event event);

// stop the thread until an event fires or until a given number of system ticks elapses
// system ticks occur every 10ms
// when the thread resumes the TCB.waitingFor will be set to the bitmask of the fired event,
// or 0x00 if the timeout happened before the event
void waitTimeout(Event event, uint16_t ticks);

// fire an event
// see enum event_t for the events that can be fired
void fireEvent(Event event);


/* monitor thread controls */

// start a monitor thread, using the provided function pointer as the thread's entry point
// the thread will be scheduled every <interval> ms, as specified by the parameter
// monitor threads are called from within an interrupt context, and therefore should NEVER
// use semaphores or wait for events
uint8_t startMonitorThread( uint8_t (*functionPtr)(void), uint16_t interval );

// start a monitor thread that calls a longjmp when its return is FALSE
// can be used to asynchronously check the condition of a while loop
// see the FrigOS wiki on GoogleCode for more examples
uint8_t startAsyncWhileThread( uint8_t (*functionPtr)(void), jmp_buf* jmpBuf );

// stop a monitor thread
void stopMonitorThread( uint8_t id);

// determine the next monitor thread that should execute and return a pointer to its MCB
// should only ever be called by the timer0 interrupt hand;er
volatile MCB* scheduleMonitorThreads(void);


/* semaphore controls */

// initialize a semaphore
// the value is the maximum number of concurrent locks the semaphore may have
// for use as a mutex value should be 1
void initSemaphore(volatile Semaphore *semaphore, uint8_t value);

// lock a semaphore
// if the semaphore is already locked the thread will be queued and be in the BLOCKED
// state until the semaphore is unlocked
// a single thread may recursively lock a semaphore without causing errors
void lockSemaphore(volatile Semaphore *semaphore);

// unlock a semaphore
// if the semaphore becomes completely unlocked the lock is assigned to the next
// thread in the queue
void unlockSemaphore(volatile Semaphore *semaphore);

// completely give up control of a semaphore
// all recursive locks are broken, and control is given to the next thread in the queue
void cedeSemaphore(volatile Semaphore *semaphore);

// see if the current thread can lock the semaphore
// will return TRUE only if the current thread already owns the lock,
// or if the semaphore is currently unlocked
BOOL tryLockSemaphore(volatile Semaphore *semaphore);


// stack initialization functions
// these generally aren't needed outside kernel.c
// see kernel.c for function descriptions
void sinit(void);
void *salloc(uint16_t stackSize);
BOOL scheck(void);
void sfree(void *stackPtr);
uint8_t talloc(void);
void tfree(uint8_t tid);


/* threadable functions */

// the idle task
// flashes the MANAGE led at regular intervals
// is used by the scheduler when all other threads are sleeping, blocked, or waiting
// since one thread MUST always be active
void idle(void *arg);

// check the stack rails and call a kernel panic if there are any stack overflows detected
void monitorStack(void *arg);

// kernel panic; the universal kill-switch
// disables all interrupts, stops all threads, and flashes all leds
// should only be used if a catastrophic emergency happens (e.g. stack overflow or
// spurious messages from servos indicating potential hardware damage)
// see enum kernel_err for error code bit-fields
void panic(uint8_t errCode);


#ifdef KERNEL_DEBUG
// sample threads and monitors that can be used for kernel debugging
void testTask0(void *arg);
void testTask1(void *arg);
void testTask2(void *arg);

uint8_t testMonitor0(void);
uint8_t testMonitor1(void);
#endif

#endif
